using Crux.Core.Dapper.SqlGenerator;
using Crux.Core.Domains;

namespace Crux.Core.Dapper;
/// <summary>
/// 
/// </summary>
/// <typeparam name="TContext"></typeparam>
public partial class QueryRepository<TContext>(
    IUnitOfWork<TContext> unitOfWork,
    IOptions<DapperConfig<TContext>> options,
    IServiceProvider provider) : IQueryRepository<TContext> where TContext :
    DapperContext
{
    /// <summary>
    /// 
    /// </summary>
    public TContext Context => unitOfWork.Context;
    private IDbConnection connection => Context.Connection;
    private IDbTransaction? Transaction => unitOfWork.Transaction;
    private readonly IOptions<DapperConfig> options = options;
    private int Timeout { get; set; } = 30;
    /// <inheritdoc/>
    public SqlGenerator<TEntity> GetSqlGenerator<TEntity>(Action<SqlGenerator<TEntity>>? configure = null) where TEntity : class
    {
        var generator = new SqlGenerator<TEntity>(options.Value);

        configure?.Invoke(generator);

        return generator;
    }
    /// <inheritdoc/>
    public IQueryRepository<TContext> SetTimeout(in int timeout)
    {
        this.Timeout = timeout;

        return this;
    }

    private TEntity? QueryFirstOrDefault<TEntity>(
        string sql,
        object? parameters = null)
    {
        var definition = new CommandDefinition(
            sql,
            commandTimeout: this.Timeout,
            transaction: Transaction,
            parameters: parameters,
            flags: CommandFlags.Buffered,
            commandType: CommandType.Text);

        var result = connection.QueryFirstOrDefault<TEntity>(definition);

        if (result is not null && result is IInjectServiceProvider entity)
        {
            entity.ServiceProvider = provider;
        }

        return result;
    }

    private IEnumerable<TEntity> Query<TEntity>(
        string sql,
        object? parameters)
    {
        var definition = new CommandDefinition(
            sql,
            commandTimeout: this.Timeout,
            transaction: Transaction,
            commandType: CommandType.Text,
            flags: CommandFlags.Buffered,
            parameters: parameters);

        var entities = connection.Query<TEntity>(definition);

        foreach (var result in entities)
        {
            if (result is not null && result is IInjectServiceProvider entity)
            {
                entity.ServiceProvider = provider;


            }
        }

        return entities;
    }

    private async Task<IEnumerable<TEntity>> QueryAsync<TEntity>(
        string sql,
        object? parameters = null,
        CancellationToken cancellationToken = default)
    {
        var definition = new CommandDefinition(
            sql,
            commandTimeout: this.Timeout,
            transaction: Transaction,
            commandType: CommandType.Text,
            cancellationToken: cancellationToken,
            flags: CommandFlags.Buffered,
            parameters: parameters);

        var entities = await connection.QueryAsync<TEntity>(definition).ConfigureAwait(false);
        foreach (var result in entities)
        {
            if (result is not null && result is IInjectServiceProvider entity)
            {
                entity.ServiceProvider = provider;
            }
        }

        return entities;
    }

    private async Task<TEntity?> QueryFirstOrDefaultAsync<TEntity>(
        string sql,
        object? parameters = null,
        CancellationToken cancellationToken = default)
    {
        var definition = new CommandDefinition(
            sql,
            commandTimeout: this.Timeout,
            transaction: Transaction,
            parameters: parameters,
            commandType: CommandType.Text,
            flags: CommandFlags.Buffered,
            cancellationToken: cancellationToken);

        var result = await connection.QueryFirstOrDefaultAsync<TEntity>(definition).ConfigureAwait(false);
        if (result is not null && result is IInjectServiceProvider entity)
        {
            entity.ServiceProvider = provider;
        }

        return result;
    }
}